package com.github.ghsea.dbtracer.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ghsea.dbtracer.Bootstrap;
import com.github.ghsea.dbtracer.util.SQLUtils;

public class RecordLogger {

	private static Logger logger = LoggerFactory.getLogger(RecordLogger.class);

	private ExecutorService executor = null;

	private static RecordLogger instance = new RecordLogger();

	private RecordLogger() {
		WorkerThreadFactory threadFactory = new WorkerThreadFactory(
				"DBTracer com.github.ghsea.dbtracer.db.RecordLogger-%d");
		executor = Executors.newSingleThreadExecutor(threadFactory);
	}

	public static RecordLogger getInstance() {
		return instance;
	}

	public void logToDB(DbHistoryLog history) {

		if (history == null) {
			return;
		}
		InsertWorker worker = new InsertWorker(history);
		executor.execute(worker);
	}

	/**
	 * 回滚(删除)transactionId所对应的数据.由于插入数据用的是线程池的队列，为防止在删除数据时，插入动作还未完成，
	 * 因此这里回滚动作也放入线程池异步执行， 同时也僻免阻塞调用方
	 */
	public void rollback(List<String> transactionIds) {
		DeleteWorker worker = new DeleteWorker(transactionIds);
		executor.execute(worker);
	}

	private static class InsertWorker implements Runnable {

		private static String SQL_INERT = "INSERT INTO db_history_log(original_key,biz_type,biz_name,chang_log,create_time,operator,transaction_id) VALUE (?,?,?,?,?,?,?)";

		private DbHistoryLog history;

		private InsertWorker(DbHistoryLog history) {
			this.history = history;
		}

		public void run() {
			ConnectionProxy ds = Bootstrap.getDataSource();
			Connection conn = null;
			PreparedStatement pst = null;
			boolean autoCommit = false;
			try {
				conn = ds.getConnection();
				autoCommit = conn.getAutoCommit();
				conn.setAutoCommit(true);

				pst = conn.prepareStatement(SQL_INERT);
				pst.setString(1, history.getKey());
				// TODO it's just dummy.FIX ME
				pst.setInt(2, 1);
				pst.setString(3, history.getBizName());

				String changeLog = DbHistoryLogUtil.createChangeLog(history);
				pst.setString(4, changeLog);
				pst.setTimestamp(5, new java.sql.Timestamp(history.getCreateTime().getTime()));
				pst.setString(6, history.getOperator());
				pst.setString(7, history.getTransactionId());
				pst.executeUpdate();

				logger.info("Log change history:" + changeLog + ",where=" + history.getWhere());
				logger.info("Log change history vo:" + history);
			} catch (SQLException e) {
				throw new RuntimeException(e);
			} finally {
				try {
					// recovery to the original mode
					conn.setAutoCommit(autoCommit);
					if (null != pst) {
						pst.close();
					}
					if (null != conn) {
						ds.releaseConnection(conn);
					}
				} catch (SQLException e) {
					logger.error(e.getMessage(), e);
				}
			}
		}
	}

	private static class DeleteWorker implements Runnable {
		private List<String> transactionIds;

		private static String SQL_DELETE = "DELETE FROM db_history_log WHERE transaction_id in ( ? )";

		public DeleteWorker(List<String> transactionIds) {
			this.transactionIds = transactionIds;
		}

		public void run() {
			if (null == transactionIds || transactionIds.isEmpty()) {
				return;
			}

			ConnectionProxy ds = Bootstrap.getDataSource();
			Connection conn = null;
			PreparedStatement pst = null;
			boolean autoCommit = false;
			try {
				conn = ds.getConnection();
				autoCommit = conn.getAutoCommit();
				conn.setAutoCommit(true);

				pst = conn.prepareStatement(SQL_DELETE);
				pst.setString(1, SQLUtils.join(transactionIds));
				pst.executeUpdate();

				logger.info("Delete db_history_log :transactionIds=" + transactionIds);
			} catch (SQLException e) {
				throw new RuntimeException(e);
			} finally {
				try {
					conn.setAutoCommit(autoCommit);
					if (null != pst) {
						pst.close();
					}
					if (null != conn) {
						ds.releaseConnection(conn);
					}
				} catch (SQLException e) {
					logger.error(e.getMessage(), e);
				}
			}
		}
	}

	static class WorkerThreadFactory implements ThreadFactory {
		private final ThreadGroup group;
		private final AtomicInteger threadNumber = new AtomicInteger(1);
		private final String threadName;

		WorkerThreadFactory(String name) {
			SecurityManager s = System.getSecurityManager();
			group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
			threadName = String.format(name, threadNumber.getAndIncrement());
		}

		public Thread newThread(Runnable r) {
			Thread t = new Thread(group, r, threadName, 0);
			if (t.getPriority() != Thread.NORM_PRIORITY)
				t.setPriority(Thread.NORM_PRIORITY);
			return t;
		}
	}

}
